<?php


namespace EchoPHP\Database\Connections;


use EchoPHP\Database\Query\Grammar\MysqlGrammar;
use EchoPHP\Database\Query\Grammar\OracleGrammar;
use EchoPHP\Database\Query\Grammar\SqlServerGrammar;
use EchoPHP\Database\TuxedoDriver\TuxedoStatement;
use InvalidArgumentException;

class TuxedoConnection extends Connection {

    protected $lastInsertId = '';

    /**
     * 获取默认语法解析器
     * @return MysqlGrammar|OracleGrammar|SqlServerGrammar
     */
    protected function getDefaultQueryGrammar() {
        $driver = $this->config['dbtype'] ?? '';
        switch (strtoupper($driver)) {
            case 'ORACLE':
                return new OracleGrammar();
            case 'MYSQL':
                return new MysqlGrammar();
            default:
                throw new InvalidArgumentException('数据库类型定义不正确');
        }
    }

    /**
     * 执行查询语句
     * @param string $query
     * @param array $bindings
     * @param int $rowNumFrom
     * @param int $rowNumTo
     * @return array
     * @throws \Exception
     */
    public function select(string $query, array $bindings = [], int $rowNumFrom = 0, int $rowNumTo = 0) {
        $query = $this->compileSelectLimit($query, $bindings, $rowNumFrom, $rowNumTo);
        return $this->run($query, $bindings, function ($query, $bindings) {
            /* @var $statement TuxedoStatement */
            $statement = $this->getConnector()->prepare($query);
            $this->bindValuesToTuxedoStatement($statement, $this->prepareBindings($bindings));
            $statement->setFetchMode($this->fetchMode);
            $statement->execute();

            return $statement->fetchAll();
        });
    }

    /**
     * 执行查询语句并返回一个数据迭代器
     * @param string $query
     * @param array $bindings
     * @param int $rowNumFrom
     * @param int $rowNumTo
     * @return \Generator
     * @throws
     */
    public function selectCursor(string $query, array $bindings = [], int $rowNumFrom = 0, int $rowNumTo = 0) {
        $query = $this->compileSelectLimit($query, $bindings, $rowNumFrom, $rowNumTo);
        /* @var $tuxedoStatement TuxedoStatement */
        $tuxedoStatement = $this->run($query, $bindings, function ($query, $bindings) {
            /* @var $statement TuxedoStatement */
            $statement = $this->getConnector()->prepare($query);
            $this->bindValuesToTuxedoStatement($statement, $this->prepareBindings($bindings));
            $statement->setFetchMode($this->fetchMode);
            $statement->execute();

            return $statement;
        });

        while ($record = $tuxedoStatement->fetch()) {
            yield $record;
        }
    }

    /**
     * 执行查询语句并返回一条结果
     * @param string $query
     * @param array $bindings
     * @return mixed
     * @throws
     */
    public function selectOne(string $query, array $bindings = []) {
        $records = $this->select($query, $bindings);

        return array_shift($records);
    }

    /**
     * 执行insert语句
     * @param string $query sql
     * @param array $bindings 绑定变量
     * @return bool 执行结果
     */
    public function insert(string $query, array $bindings = []) {
        return $this->execute($query, $bindings);
    }

    /**
     * 执行update语句
     * @param string $query
     * @param array $bindings
     * @return int 受影响行数
     */
    public function update(string $query, array $bindings = []) {
        return $this->executeWithAffectedRows($query, $bindings);
    }

    /**
     * 执行一条删除语句
     * @param string $query
     * @param array $bindings
     * @return int 受影响行数
     */
    public function delete(string $query, array $bindings = []) {
        return $this->executeWithAffectedRows($query, $bindings);
    }

    /**
     * 获取执行语句
     * @param string $query
     * @param array $bindings
     * @return TuxedoStatement
     * @throws \Exception
     */
    public function prepareStatement(string $query, array $bindings = []) {
        $statement = $this->getConnector()->prepare($query);
        $this->bindValuesToTuxedoStatement($statement, $this->prepareBindings($bindings));

        return $statement;
    }

    /**
     * 准备单库事务处理语句集
     * @param bool $rollbackIfNoAffects
     * @param TuxedoStatement ...$tuxedoStatements
     * @return \EchoPHP\Database\TuxedoDriver\TuxedoTransactionStatement
     */
    public function prepareTransactionStatement(bool $rollbackIfNoAffects, TuxedoStatement ...$tuxedoStatements) {
        $tuxedoTransactionStatement = $this->getConnector()->prepareTransaction($rollbackIfNoAffects, $tuxedoStatements);

        return $tuxedoTransactionStatement;
    }

    /**
     * 执行一条操作语句，update类型操作
     * @param string $query
     * @param array $bindings
     * @return bool 执行失败
     */
    public function execute(string $query, array $bindings = []) {
        return $this->run($query, $bindings, function ($query, $bindings) {
            /* @var $statement TuxedoStatement */
            $statement = $this->getConnector()->prepare($query);
            $this->bindValuesToTuxedoStatement($statement, $this->prepareBindings($bindings));
            $executeResult = $statement->execute();
            $this->lastInsertId = $statement->getLastInsertId();

            return $executeResult;
        });
    }

    /**
     *  执行一条操作语句，update类型操作
     * @param string $query
     * @param array $bindings
     * @return mixed
     */
    public function executeWithAffectedRows(string $query, array $bindings = []) {
        return $this->run($query, $bindings, function ($query, $bindings) {
            /* @var $statement TuxedoStatement */
            $statement = $this->getConnector()->prepare($query);
            $this->bindValuesToTuxedoStatement($statement, $this->prepareBindings($bindings));
            $statement->execute();
            $this->lastInsertId = $statement->getLastInsertId();

            return $statement->getAffectedRowCount();
        });
    }

    /**
     * 获取最后插入的id
     * @param null $sequence
     * @return mixed
     */
    public function getLastInsertId($sequence = null) {
        return $this->lastInsertId;
    }

    /**
     * TuxedoStatement 绑定变量
     * @param TuxedoStatement $statement
     * @param array $bindings
     */
    protected function bindValuesToTuxedoStatement(TuxedoStatement $statement, array $bindings) {
        foreach ($bindings as $key => $value) {
            if (is_null($value)) {
                $statement->bindValue($value, TuxedoStatement::PARAM_NULL);
            } elseif (is_bool($value)) {
                $statement->bindValue($value, TuxedoStatement::PARAM_BOOL);
            } elseif (is_int($value)) {
                $statement->bindValue($value, TuxedoStatement::PARAM_INT);
            } else {
                $statement->bindValue($value, TuxedoStatement::PARAM_STR);
            }
        }
    }

}